/**   
 * @Title: MemoryGfHost.java 
 * @Package net.gdface.service.search 
 * @Description: TODO 
 * @author guyadong   
 * @date 2015年5月1日 下午4:54:37 
 * @version V1.0   
 */
package net.gdface.httpclient;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static net.gdface.db.DaoManagement.DAO;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.facelib.u4fdb.localdb.HostBean;
import net.gdface.db.BackupHook;

/**
 * 单实例对象<br>
 * 管理访问过的主机地址，对每个主机的访问次数成功率进行统计 记录每个主机的上次访问时间 <br>
 * 所有方法都是线程安全的
 * 
 * @author guyadong
 *
 */
public class HostManager implements BackupHook {
	protected static final Logger logger = LoggerFactory.getLogger(HostManager.class);
	
	/**
	 * 默认的访问时间间隔(毫秒)
	 */
	public static final int DEFAULT_INTERVAL_MILLS = 200;
	public static final int DEFAULT_PAGE_INTERVAL_MILLS = 4000;
	public static final int DEFAULT_REST_COUNT=3;
	public static final int DEFAULT_REST_TIMES=0;
	public static final int VALID_SAMPLE_NUM = 100;
	public static final float FAIL_RATIO = 1.0f;

	// /////////////////////////////////////////////
	private static final int initialCapacity = 1024*4;
	private static final float loadFactor = 0.75F;
	private static final int concurrencyLevel = 256;
	/**
	 * 默认的访问时间间隔(毫秒)
	 */
	private static long defaultIntervalMills = DEFAULT_INTERVAL_MILLS;
	private static int validSampleNum = VALID_SAMPLE_NUM;
	private static float failRatio = FAIL_RATIO;
	private static final HostManager instance = new HostManager();
	/**
	 * 所有访问过的主机列表，String为代表主机的url,只有protocol,host,port部分
	 */
	private final ConcurrentMap<String, Data> hosts = new ConcurrentHashMap<String, Data>(initialCapacity,
			loadFactor, concurrencyLevel);

	/**
	 * 主机访问统计数据对象
	 */
	@SuppressWarnings("serial")
	private class Data extends HostBean{
		final AtomicInteger total = new AtomicInteger(0);
		final AtomicInteger sucess = new AtomicInteger(0);
		final AtomicInteger fail = new AtomicInteger(0);
		final AtomicLong lastHitTime = new AtomicLong(0L);

		Data(HostBean taskBean){
			copy(taskBean);
			this.sucess.set(firstNonNull(getSucess(), 0));
			this.fail.set(firstNonNull(getFail(), 0));			
		}
		Data update(){
			setSucess(sucess.get());
			setFail(fail.get());
			return this;
		}
	}

	/**
	 * 单实例对象
	 */
	private HostManager() {
		// 加载所有host记录
		DAO.daoLoadHostAll((bean)->hosts.put(bean.getHost(), new Data(bean)));
		
	}
    /** Base of nanosecond timings, to avoid wrapping */
    private static final long NANO_ORIGIN = System.nanoTime();

    /**
     * Returns nanosecond time offset by origin
     */
    final static long now() {
        return System.nanoTime() - NANO_ORIGIN;
    }
	/**
	 * 从{@code uri}中分隔出协议，主机，端口组织新的代表主机的{@code URI}<br>
	 * 如果端口号为协议的默认端口，则在新的{@code String}中去掉端口号
	 * 
	 * @param uri
	 * @return
	 */
	private static String newHostURI(URI uri) {
		URI host ;
		try {
			int port = uri.getPort();
			host = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), port,null, null, null);
			if(host.toURL().getDefaultPort()==port)
				host = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1,null, null, null);
		} catch (URISyntaxException e) {
			try {
				host= new URI(uri.getScheme(),uri.getAuthority(),null,null,null);
			} catch (URISyntaxException e1) {
				throw new RuntimeException(String.format("uri=[%s] %s",uri.toString(),e.toString()),e);
			}			
		} catch (MalformedURLException e) {
			throw new RuntimeException(String.format("uri=[%s] %s",uri.toString(),e.toString()),e);
		}
		return host.toString();
	}

	/**
	 * 记录每个host访问成功失败的次数和上次访问时间
	 * 
	 * @param uri
	 * @param isSucess
	 *            true 正常返回 false 异常
	 */
	public void hitInc(URI uri, boolean isSucess) {
		Data stat = getData(uri);
		stat.total.incrementAndGet();
		if (isSucess){
			stat.sucess.incrementAndGet();
		} else{
			stat.fail.incrementAndGet();
		}
	}

	/**
	 * 记录每个host访问成功失败的次数
	 * 
	 * @param url
	 * @param isSucess
	 * @throws URISyntaxException
	 */
	public void hitInc(String url, boolean isSucess) throws URISyntaxException {
		hitInc(new URI(url), isSucess);
	}

	/**
	 * 如果当前时间与上次访问的时间差大于指定的间隔值，就将对象更新
	 * 
	 * @param lastTime
	 *            上次访问时间对象
	 * @param intervalMills
	 *            时间间隔(毫秒)
	 * @return 返回与指定时间隔音的差值(毫秒)<br>
	 *         <0 时间未到，没有更新 <br>
	 *         >=0 时间到AtomicLong对象被更新
	 */
	private static long getIntervalAndSetIfTimeup(AtomicLong lastTime, long intervalMills) {
		for (;;) {
			long last = lastTime.get();
//			long current = now();
//			long gap = TimeUnit.MILLISECONDS.convert(current - TimeUnit.NANOSECONDS.convert(intervalMills, TimeUnit.MILLISECONDS) - last,TimeUnit.NANOSECONDS);
			long current = System.currentTimeMillis();
			long gap = current - intervalMills - last;
			if (gap < 0)
				return gap;
			else if (lastTime.compareAndSet(last, current))
				return gap;
		}
	}

	/**
	 * 判断当前时间距离{@code uri}所在的主机上次访问时间间隔是否已经达到指定的值{@code intervalMills },<br>
	 * 如果达到(或超过)就将当前更新更新到主机上次访问时间，否则不更新<br>
	 * 当{@code restCount!=Integer.MAX_VALUE}时,休息机制启动:<br>
	 * 在固定间隔{@code intervalMills}访问主机次数达到{@code restCount}倍数后，加大间隔时间，<br>
	 * 由{@code restTimes}指定加大的倍数
	 * 
	 * @param uri
	 *            {@code URI}
	 * @param intervalMills
	 *            时间间隔(毫秒) 如果小于0，则使用默认值 {@link #defaultIntervalMills}
	 * @param restCount
	 *            休息计数器,对主机的访问计数（成功+失败）达到该数字的整数倍时，延长间隔时间<br>
	 *            小于0时 使用缺省值 {@link #DEFAULT_REST_COUNT}<br>
	 *            为{@code Integer.MAX_VALUE}时,休息机制无效
	 * @param restTimes
	 *            休息时间倍数，{@code restCount}达到休息次数时，休息时间的倍数<br>
	 *            小于等于0时，使用restCount做为休息倍数，
	 * @return 参见 {@link #getIntervalAndSetIfTimeup(AtomicLong, long)}
	 * @see #getIntervalAndSetIfTimeup(AtomicLong, long)
	 */
	public long setHitIfTimeup(URI uri, long intervalMills, int restCount, int restTimes) {
		Data last = getData(uri);
		AtomicLong along = last.lastHitTime;
		if (intervalMills < 0)
			intervalMills = defaultIntervalMills;
		/*if(restCount<=0)
			restCount=DEFAULT_REST_COUNT;
		if ((restCount!=Integer.MAX_VALUE) && (0 == (last.total.get() + 1) % restCount)) {
			intervalMills = intervalMills * (restTimes > 0 ? restTimes : restCount);
		}*/
		return getIntervalAndSetIfTimeup(along, intervalMills);
	}
	
	/**
	 * 判断当前时间距离{@code url}所在的主机上次访问时间间隔是否已经达到指定的值{@code intervalMills },<br>
	 * 如果达到(或超过)就将当前更新更新到主机上次访问时间，否则不更新
	 * 
	 * @param uri
	 * @param intervalMills 间隔时间(毫秒)<br>小于0时使用缺省值 {@link #defaultIntervalMills}
	 * @return
	 * @see #setHitIfTimeup(URI, long, int, int)
	 */
	public long setHitIfTimeup(URI uri, long intervalMills) {
		return getIntervalAndSetIfTimeup(getData(uri).lastHitTime, intervalMills<0?defaultIntervalMills:intervalMills);
	}

	/**
	 * 设置将当前时间设置为最新的主机上次访问时间间隔{@link Data#lastHitTime}<br>
	 * 
	 * @param uri
	 * @return 当前时间与上{@link Data#lastHitTime}旧值的差值
	 * @see #setHitIfTimeup(URI, long, int, int)
	 */
	public long setHit(URI uri) {
		return getIntervalAndSetIfTimeup(getData(uri).lastHitTime, 0L);
	}
		
	
	/**从{@link #hosts}中获取指定{@code URI}的统计数据对象,如果不存在，就新建一个。
	 * @param uri
	 * @return 统计数据对象 {@link net.gdface.httpclient.HostManager.Data}
	 */
	private Data getData(URI uri) {
		HostBean stat = HostBean.builder().host(newHostURI(checkNotNull(uri,"uri is null"))).build();
		HostBean exists = DAO.daoAddHostIfAbsent(stat);
		return new Data(exists);
	}

	/**
	 * 如果当前时间 与url的主机上次访问时间的间隔达不到指定的值就{@code sleep}
	 * 当访问主机的次数达到{@code restCount}的整数倍时，启动休息机制,参见{@link #setHitIfTimeup(URI, long, int, int)}
	 * @param uri
	 * @param intervalMills
	 * @param restCount 参见{@link #setHitIfTimeup(URI, long, int, int)}
	 * @param restTimes 参见{@link #setHitIfTimeup(URI, long, int, int)}
	 * @return
	 * @throws InterruptedException
	 * @see #setHitIfTimeup(URI, long, int, int)
	 */
	public long sleepUnlessTimeup(URI uri, long intervalMills, int restCount, int restTimes) throws InterruptedException {
		for (;;) {
			long gap = setHitIfTimeup(uri, intervalMills, restCount, restTimes);
			if (gap < 0)
				Thread.sleep(-gap);
			else
				return gap;
		}
	}

	/**
	 * @param defaultIntervalMills
	 *            要设置的 defaultIntervalMills
	 */
	public static void setDefaultIntervalMills(long defaultIntervalMills) {
		HostManager.defaultIntervalMills = defaultIntervalMills;
	}

	/**
	 * 根据统计数据判断主机连接质量
	 * 
	 * @param uri
	 * @return true 可连接 <br>
	 *         false 不可连接
	 */
	public boolean isALiveHost(URI uri) {
		Data stat = hosts.get(newHostURI(uri));
		if (null != stat) {
			int fail = stat.fail.get();
			int sucess = stat.sucess.get();
			return !((fail + sucess) > validSampleNum && (fail / (float)(fail + sucess) >= failRatio));
		} else
			return true;
	}

	@Override
	public synchronized void onPersistDB() {
		Iterator<Entry<String, Data>> it = hosts.entrySet().iterator();
		int count=0;
		while (it.hasNext()) {
			Entry<String, Data> host = it.next();
			Data bean = host.getValue();
			if (bean.beModified()){
				count++;
			}
			DAO.daoSaveHost(bean.update());
		}
		logger.info("HOSTMANAGER {} rows write back",count);	
	}

	/**
	 * @return instance
	 */
	public static HostManager getInstance() {
		return instance;
	}
}
